package fr.pud.server;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;

import fr.pud.client.model.Fleet;
import fr.pud.client.model.PUDStation;
import fr.pud.client.model.Queries;
import fr.pud.client.model.Query;
import fr.pud.client.model.Route;
import fr.pud.client.model.Vehicle;
import fr.pud.utils.Properties;

/**
 * This class implements the computation to solve a PUD problem.
 * It uses ALNS_DARPT solver (developed by Renaud Masson) which has to be in
 * "libraries/" folder.
 * 10 avr. 2012 - tsp-osm
 * @author Simon Devineau, Adrien Hierry, Leopold Houdin, Pierre Reliquet
 *         Ecole des Mines de Nantes
 *         Solver.java
 */
public class Solver {
    public static final ArrayList<Route> ERROR     = null;
    public static final String           SEPARATOR = "\t";
    public static final String           NEWLINE   = "\n";
    public static final String           MINUS     = "-";
    public static final double           MAX_DIST  = 86400.0;

    /**
     * Computes the largest window for all vehicles.
     * @param fleet
     *            the vehicle.
     * @return the largest window.
     */
    public static int[] getStationWindow(Fleet fleet) {
        int min = 86400;
        int max = 0;
        for (Vehicle v : fleet) {
            int o = v.getStartingPoint().getOpening().getFullSeconds();
            int c = v.getEndingPoint().getClosing().getFullSeconds();
            min = o < min ? o : min;
            max = c > max ? c : max;
        }
        return new int[] {
                min, max
        };
    }

    /**
     * Solves the problem given in parameter.
     * It creates a new folder in "data/" folder which has an unique id (i.e.
     * the timestamp of the call).
     * It creates three instance files : instance, positions and matrix.
     * It creates a solution file : solution.
     * @param fleet
     *            the fleet.
     * @param queries
     *            the queries.
     * @param properties
     *            the properties.
     * @return an array of the routes.
     */
    public static ArrayList<Route> solve(Fleet fleet, Queries queries,
            Properties properties) {
        File data = new File("data");
        if (!data.exists()) {
            data.mkdir();
        }
        String folder = "data/" + new Date().getTime() + "/";
        // Creating directory
        System.out.print("Creating " + folder + " folder... ");
        new File(folder).mkdir();
        System.out.println("success");
        System.out.println();
        // Create files
        System.out.print("Creating " + folder + "instance file... ");
        File instanceFile = new File(folder + "instance");
        try {
            instanceFile.createNewFile();
        }
        catch (IOException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        System.out.println("success");
        System.out.print("Creating " + folder + "positions file... ");
        File positionsFile = new File(folder + "positions");
        try {
            positionsFile.createNewFile();
        }
        catch (IOException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        System.out.println("success");
        System.out.print("Creating " + folder + "matrix file... ");
        File matrixFile = new File(folder + "matrix");
        try {
            matrixFile.createNewFile();
        }
        catch (IOException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        System.out.println("success");
        System.out.println();
        // Relocate nodes
        System.out.print("Relocating nodes... ");
        int nodes_count = 0;
        for (Vehicle v : fleet) {
            OSRMTools.computeNearestPoint(v.getStartingPoint());
            OSRMTools.computeNearestPoint(v.getEndingPoint());
            nodes_count += 2;
        }
        for (Query q : queries) {
            OSRMTools.computeNearestPoint(q.getPickup());
            OSRMTools.computeNearestPoint(q.getDelivery());
            nodes_count += 2;
        }
        System.out.println("success");
        System.out.println(nodes_count + " nodes have been relocated");
        System.out.println();
        BufferedWriter instance, positions, matrix;
        ArrayList<PUDStation> locations = new ArrayList<PUDStation>();
        try {
            // Create writers
            instance = new BufferedWriter(new FileWriter(instanceFile));
            positions = new BufferedWriter(new FileWriter(positionsFile));
            matrix = new BufferedWriter(new FileWriter(matrixFile));
            // Associate each PUDStation with an internal id (i.e. index of the
// PUDStation in the array)
            System.out.print("Associating nodes... ");
            for (Vehicle v : fleet) {
                if (!locations.contains(v.getStartingPoint())) {
                    locations.add(v.getStartingPoint());
                }
                if (!locations.contains(v.getEndingPoint())) {
                    locations.add(v.getEndingPoint());
                }
            }
            for (Query q : queries) {
                if (!locations.contains(q.getPickup())) {
                    locations.add(q.getPickup());
                }
                if (!locations.contains(q.getDelivery())) {
                    locations.add(q.getDelivery());
                }
            }
            System.out.println("success");
            System.out.println();
            // Compute distances matrix
            System.out.println("Computing distances matrix... ");
            double[][] table = OSRMTools.computeDistanceMatrix(locations);
            // Checking distances matrix
            System.out.print("Checking distances matrix... ");
            int approximated_distances = 0;
            int size = table.length;
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
                    if (table[i][j] > Solver.MAX_DIST) {
                        if (table[j][i] > Solver.MAX_DIST) {
                        }
                        else {
                            table[i][j] = table[j][i];
                        }
                        approximated_distances++;
                    }
                }
            }
            System.out.println("success");
            System.out.println(approximated_distances
                    + " approximated distances");
            System.out.println();
            System.out.println("Writing files...");
            // Write instance header
            System.out.print("\tWriting instance header... ");
            int[] stationLimits = Solver.getStationWindow(fleet);
            instance.write(fleet.getVehiclesNb() + Solver.SEPARATOR);
            instance.write(queries.getQueries().size() + Solver.SEPARATOR);
            instance.write(0 + Solver.SEPARATOR);
            instance.write(0 + Solver.SEPARATOR);
            instance.write(stationLimits[0] + Solver.SEPARATOR);
            instance.write(stationLimits[1] + Solver.SEPARATOR + Solver.NEWLINE);
            System.out.println("success");
            // Write positions file
            System.out.print("\tWriting positions... ");
            positions.write(locations.size() + Solver.NEWLINE);
            for (PUDStation s : locations) {
                positions.write(locations.indexOf(s) + Solver.SEPARATOR
                        + s.getAddress().getCoordinate().getLat()
                        + Solver.SEPARATOR
                        + s.getAddress().getCoordinate().getLon()
                        + Solver.NEWLINE);
            }
            System.out.println("success");
            // Write vehicles in instance file
            System.out.print("\tWriting vehicles... ");
            for (Vehicle v : fleet) {
                instance.write(locations.indexOf(v.getStartingPoint())
                        + Solver.SEPARATOR
                        + v.getStartingPoint().getOpening().getFullSeconds()
                        + Solver.SEPARATOR
                        + v.getStartingPoint().getClosing().getFullSeconds()
                        + Solver.SEPARATOR
                        + locations.indexOf(v.getEndingPoint())
                        + Solver.SEPARATOR
                        + v.getEndingPoint().getOpening().getFullSeconds()
                        + Solver.SEPARATOR
                        + v.getEndingPoint().getClosing().getFullSeconds()
                        + Solver.SEPARATOR + v.getCapacity() + Solver.SEPARATOR
                        + v.getMaxDriveDuration() * 60 + Solver.NEWLINE);
            }
            System.out.println("success");
            // Write requests in instance file
            System.out.print("\tWriting queries... ");
            for (Query r : queries) {
                instance.write(locations.indexOf(r.getPickup())
                        + Solver.SEPARATOR + r.getQuantity() + Solver.SEPARATOR
                        + r.getPickup().getLoadingTime() * 60
                        + Solver.SEPARATOR
                        + r.getPickup().getOpening().getFullSeconds()
                        + Solver.SEPARATOR
                        + r.getPickup().getClosing().getFullSeconds()
                        + Solver.SEPARATOR + r.getMaxDriveDuration() * 60
                        + Solver.NEWLINE + locations.indexOf(r.getDelivery())
                        + Solver.SEPARATOR + Solver.MINUS + r.getQuantity()
                        + Solver.SEPARATOR
                        + r.getDelivery().getLoadingTime() * 60
                        + Solver.SEPARATOR
                        + r.getDelivery().getOpening().getFullSeconds()
                        + Solver.SEPARATOR
                        + r.getDelivery().getClosing().getFullSeconds()
                        + Solver.NEWLINE);
            }
            System.out.println("success");
            // Write matrix file
            System.out.print("\tWriting distances matrix... ");
            matrix.write(locations.size() + Solver.NEWLINE);
            String toWrite = "";
            for (int i = 0; i < table.length; i++) {
                for (int j = 0; j < table.length; j++) {
                    toWrite += table[i][j] + Solver.SEPARATOR;
                }
                toWrite = toWrite.substring(0, toWrite.length() - 1);
                toWrite += Solver.NEWLINE;
            }
            matrix.write(toWrite);
            System.out.println("success");
            // Close writers
            instance.close();
            positions.close();
            matrix.close();
        }
        catch (IOException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        System.out.println();
        try {
            String[] args = {
                    "/bin/sh",
                    "-c",
                    "libraries/ALNS_DARPT "
                            + folder
                            + "matrix "
                            + folder
                            + "instance "
                            + folder
                            + "positions "
                            + (properties.getMinimizeFleetSize() ? 0 : fleet
                                    .getVehiclesNb()) + " "
                            + properties.getExecDuration() + " " + folder
                            + "solution " + ">> " + folder + "log"
            };
            System.out.println(args[2]);
            System.out.print("Solving... ");
            Runtime.getRuntime().exec(args).waitFor();
        }
        catch (InterruptedException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        catch (IOException e) {
            System.out.println("fail");
            return Solver.ERROR;
        }
        System.out.println("success");
        System.out.println();
        return SolParser.parse(folder + "solution", locations);
    }
}
